github_qr_code()
submit choice Burj Khalifa - Dubai¶App.box_*() APIApp.box_create(name, size) - any size ($\leq$ 32KB)¶App.box_length(name) - opcode box_len¶App.box_delete(name) - opcode box_del¶App.box_replace(name, idx, L) - set part of box¶App.box_extract(name, idx, L) - get part of box¶App.box_put(name, value) - set value (may create box)¶App.box_get(name) - get everything (fail if size > 4KB)¶App.box_get() and App.box_put()¶from pyteal import Int, Itob, Txn, App, Seq, If, Assert, Btoi
# App.box: account --> choice
# App.global: choice --> count
num_choices = Int(7)
choice = Itob(Int(1)) # index for "Burj Khalifa - Dubai"
box_key = Txn.sender() # sender's account
previous = App.box_get(box_key) # MaybeValue
submit_expr = Seq(
Assert(Btoi(choice) < num_choices),
If(previous.hasValue()).Then(
App.globalPut( # App.global[previous] -= 1 :
previous.value(),
App.globalGet(previous.value()) - Int(1)
),
),
App.box_put(box_key, choice), # App.box[box_key] <-- choice
# App.global[choice] += 1 :
App.globalPut(choice, App.globalGet(choice) + Int(1)),
)
methods - allow interacting with Poll App¶delete for reasons to be explained shortly)¶OnComplete Actions for App Transactions¶Router and its M.O.E. Questions¶moe()
Router and its M.O.E. Questions¶Router constructs the Teal code necessary to delegate application transactions to either a bare app call action or a method based on answers to:¶bare app call? If not, which method selected?¶OnComplete is requested?¶exists? (Conversely, being created?)¶Router Initialization¶warning()
# WARNING: STUBS ARE FOR ROUTER-ILLUSTRATION PURPOSES ONLY!!!
del_action = OnCompleteAction.call_only(Seq())
router = Router(
name="OpenPollingApp",
descr="This is a polling application.",
bare_calls=BareCallActions(delete_application=del_action),
)
approval, clear, json_contract = router.compile_program(version=8)
JSON Contract (with no methods)¶print(json.dumps(json_contract.dictify(), indent=2))
{
"name": "OpenPollingApp",
"methods": [],
"networks": {},
"desc": "This is a polling application."
}
approval, clear, json_contract = router.compile_program(version=8)
print(approval)
#pragma version 8 txn NumAppArgs int 0 == bnz main_l2 err main_l2: txn OnCompletion int DeleteApplication == bnz main_l4 err main_l4: txn ApplicationID int 0 != assert int 1 return
delete¶show() # custom method showing relevant Teal
txn NumAppArgs
int 0
==
bnz main_l2
. . .
main_l2:
txn OnCompletion
int DeleteApplication
==
bnz main_l4
. . .
main_l4:
txn ApplicationID
int 0
!=
assert
int 1
return
methods to route with @router.method¶open() and close()¶@router.method(name="open")
def open_poll() -> Expr:
"""Marks this poll as open."""
return Seq()
@router.method(name="close")
def close_poll() -> Expr:
"""Marks this poll as closed."""
return Seq()
warning()
approval, clear, json_contract = router.compile_program(version=8)
print(json.dumps(json_contract.dictify(), indent=2))
{
"name": "OpenPollingApp",
"methods": [
{
"name": "open",
"args": [],
"returns": {
"type": "void"
},
"desc": "Marks this poll as open."
},
{
"name": "close",
"args": [],
"returns": {
"type": "void"
},
"desc": "Marks this poll as closed."
}
],
"networks": {},
"desc": "This is a polling application."
}
PyTeal Type |
ARC-4 Type |
Dynamic / Static |
Description |
|---|---|---|---|
|
Static |
An 8-bit unsigned integer |
|
|
IFF |
A fixed-length array with N elements |
|
|
Dynamic |
Variable-length byte array |
@router.method
def submit(choice: abi.Uint8) -> Expr:
"""Submit a response to the poll.
Args:
choice: The choice made by the sender.
"""
return Seq()
warning()
opts = OptimizeOptions(scratch_slots=True)
(approval, clear, json_contract) = \
router.compile_program(version=8, optimize=opts)
# ------------------------------------^^^^^^^^^^^^^
submit(choice) portion of JSON contract¶show() # lines 20-33 of the JSON contract
{
"name": "submit",
"args": [
{
"type": "uint8",
"name": "choice",
"desc": "The choice made by the sender."
}
],
"returns": {
"type": "void"
},
"desc": "Submit a response to the poll."
}
submit(choice)¶show()
txna ApplicationArgs 0
method "submit(uint8)void"
==
bnz main_l5
. . .
main_l5:
txn OnCompletion
int NoOp
==
txn ApplicationID
int 0
!=
&&
assert
txna ApplicationArgs 1
int 0
getbyte
callsub submit_2
. . .
// submit
submit_2:
store 0
retsub
contract/contract.py¶show()
# This example is provided for informational purposes only and has not been audited for security.
import json
from typing import Literal
from pyteal import *
on_delete = Seq(
Assert(Txn.sender() == Global.creator_address()),
InnerTxnBuilder.Execute(
{
TxnField.type_enum: TxnType.Payment,
TxnField.close_remainder_to: Txn.sender(),
}
),
)
router = Router(
name="OpenPollingApp",
descr="A polling application with no restrictions on who can participate.",
bare_calls=BareCallActions(
delete_application=OnCompleteAction.call_only(on_delete)
),
)
NUM_OPTIONS = 7
open_key = Bytes(b"open")
resubmit_key = Bytes(b"resubmit")
question_key = Bytes(b"question")
option_name_prefix = b"option_name_"
option_name_keys = [Bytes(option_name_prefix + bytes([i])) for i in range(NUM_OPTIONS)]
option_count_prefix = b"option_count_"
option_count_keys = [
Bytes(option_count_prefix + bytes([i])) for i in range(NUM_OPTIONS)
]
@router.method(no_op=CallConfig.CREATE)
def create(
question: abi.String, options: abi.StaticArray[abi.String, Literal[NUM_OPTIONS]], can_resubmit: abi.Bool # type: ignore[valid-type]
) -> Expr:
"""Create a new polling application.
Args:
question: The question this poll is asking.
options: A list of options for the poll. This list should not contain duplicate entries.
can_resubmit: Whether this poll allows accounts to change their submissions or not.
"""
name = abi.make(abi.String)
return Seq(
App.globalPut(open_key, Int(0)),
App.globalPut(resubmit_key, can_resubmit.get()),
App.globalPut(question_key, question.get()),
*[
Seq(
name.set(options[i]),
App.globalPut(option_name_keys[i], name.get()),
App.globalPut(option_count_keys[i], Int(0)),
)
for i in range(NUM_OPTIONS)
],
)
@router.method(name="open")
def open_poll() -> Expr:
"""Marks this poll as open.
This will fail if the poll is already open.
The poll must be open in order to receive user input.
"""
return Seq(
Assert(Not(App.globalGet(open_key))),
App.globalPut(open_key, Int(1)),
)
@router.method(name="close")
def close_poll() -> Expr:
"""Marks this poll as closed.
This will fail if the poll is already closed.
"""
return Seq(
Assert(App.globalGet(open_key)),
App.globalPut(open_key, Int(0)),
)
@router.method
def submit(choice: abi.Uint8) -> Expr:
"""Submit a response to the poll.
Submissions can only be received if the poll is open. If the poll is closed, this will fail.
If a submission has already been made by the sender and the poll allows resubmissions, the
sender's choice will be updated to the most recent submission. If the poll does not allow
resubmissions, this action will fail.
Args:
choice: The choice made by the sender. This must be an index into the options for this poll.
"""
new_choice_count_key = ScratchVar(TealType.bytes)
old_choice_count_key = ScratchVar(TealType.bytes)
return Seq(
Assert(choice.get() < Int(NUM_OPTIONS)),
new_choice_count_key.store(
SetByte(option_count_keys[0], Int(len(option_count_prefix)), choice.get())
),
sender_box := App.box_get(Txn.sender()),
If(sender_box.hasValue()).Then(
# the sender has already submitted a response, so it must be cleared
Assert(App.globalGet(resubmit_key)),
old_choice_count_key.store(
SetByte(
option_count_keys[0],
Int(len(option_count_prefix)),
Btoi(sender_box.value()),
)
),
App.globalPut(
old_choice_count_key.load(),
App.globalGet(old_choice_count_key.load()) - Int(1),
),
),
App.box_put(Txn.sender(), choice.encode()),
App.globalPut(
new_choice_count_key.load(),
App.globalGet(new_choice_count_key.load()) + Int(1),
),
)
class PollStatus(abi.NamedTuple):
question: abi.Field[abi.String]
can_resubmit: abi.Field[abi.Bool]
is_open: abi.Field[abi.Bool]
results: abi.Field[abi.StaticArray[abi.Tuple2[abi.String, abi.Uint64], Literal[NUM_OPTIONS]]] # type: ignore[valid-type]
@router.method
def status(*, output: PollStatus) -> Expr:
"""Get the status of this poll.
Returns:
A tuple containing the following information, in order: the question is poll is asking,
whether the poll allows resubmission, whether the poll is open, and an array of the poll's
current results. This array contains one entry per option, and each entry is a tuple of that
option's value and the number of accounts who have voted for it.
"""
question = abi.make(abi.String)
can_resubmit = abi.make(abi.Bool)
is_open = abi.make(abi.Bool)
option_name = abi.make(abi.String)
option_count = abi.make(abi.Uint64)
partial_results = [
abi.make(abi.Tuple2[abi.String, abi.Uint64]) for i in range(NUM_OPTIONS)
]
results = abi.make(abi.StaticArray[abi.Tuple2[abi.String, abi.Uint64], Literal[NUM_OPTIONS]]) # type: ignore[valid-type]
return Seq(
question.set(App.globalGet(question_key)),
can_resubmit.set(App.globalGet(resubmit_key)),
is_open.set(App.globalGet(open_key)),
*[
Seq(
option_name.set(App.globalGet(option_name_keys[i])),
option_count.set(App.globalGet(option_count_keys[i])),
partial_results[i].set(option_name, option_count),
)
for i in range(NUM_OPTIONS)
],
results.set(partial_results),
output.set(question, can_resubmit, is_open, results),
)
contract/contract.json¶show()
{
"name": "OpenPollingApp",
"methods": [
{
"name": "create",
"args": [
{
"type": "string",
"name": "question",
"desc": "The question this poll is asking."
},
{
"type": "string[7]",
"name": "options",
"desc": "A list of options for the poll. This list should not contain duplicate entries."
},
{
"type": "bool",
"name": "can_resubmit",
"desc": "Whether this poll allows accounts to change their submissions or not."
}
],
"returns": {
"type": "void"
},
"desc": "Create a new polling application."
},
{
"name": "open",
"args": [],
"returns": {
"type": "void"
},
"desc": "Marks this poll as open.\nThis will fail if the poll is already open.\nThe poll must be open in order to receive user input."
},
{
"name": "close",
"args": [],
"returns": {
"type": "void"
},
"desc": "Marks this poll as closed.\nThis will fail if the poll is already closed."
},
{
"name": "submit",
"args": [
{
"type": "uint8",
"name": "choice",
"desc": "The choice made by the sender. This must be an index into the options for this poll."
}
],
"returns": {
"type": "void"
},
"desc": "Submit a response to the poll.\nSubmissions can only be received if the poll is open. If the poll is closed, this will fail.\nIf a submission has already been made by the sender and the poll allows resubmissions, the sender's choice will be updated to the most recent submission. If the poll does not allow resubmissions, this action will fail."
},
{
"name": "status",
"args": [],
"returns": {
"type": "(string,bool,bool,(string,uint64)[7])",
"desc": "A tuple containing the following information, in order: the question is poll is asking, whether the poll allows resubmission, whether the poll is open, and an array of the poll's current results. This array contains one entry per option, and each entry is a tuple of that option's value and the number of accounts who have voted for it."
},
"desc": "Get the status of this poll."
contract/approval.teal¶show()
#pragma version 8
txn NumAppArgs
int 0
==
bnz main_l12
txna ApplicationArgs 0
method "create(string,string[7],bool)void"
==
bnz main_l11
txna ApplicationArgs 0
method "open()void"
==
bnz main_l10
txna ApplicationArgs 0
method "close()void"
==
bnz main_l9
txna ApplicationArgs 0
method "submit(uint8)void"
==
bnz main_l8
txna ApplicationArgs 0
method "status()(string,bool,bool,(string,uint64)[7])"
==
bnz main_l7
err
main_l7:
txn OnCompletion
int NoOp
==
txn ApplicationID
int 0
!=
&&
assert
callsub status_4
store 3
byte 0x151f7c75
load 3
concat
log
int 1
return
main_l8:
txn OnCompletion
int NoOp
==
txn ApplicationID
int 0
!=
&&
assert
txna ApplicationArgs 1
int 0
getbyte
callsub submit_3
int 1
return
main_l9:
txn OnCompletion
int NoOp
==
txn ApplicationID
int 0
!=
&&
assert
callsub close_2
int 1
return
main_l10:
txn OnCompletion
int NoOp
==
txn ApplicationID
int 0
!=
&&
assert
callsub open_1
int 1
return
main_l11:
txn OnCompletion
int NoOp
==
txn ApplicationID
int 0
==
&&
assert
txna ApplicationArgs 1
store 0
txna ApplicationArgs 2
store 1
txna ApplicationArgs 3
int 0
int 8
*
getbit
store 2
load 0
load 1
load 2
callsub create_0
int 1
return
main_l12:
txn OnCompletion
int DeleteApplication
==
bnz main_l14
err
main_l14:
txn ApplicationID
int 0
!=
assert
txn Sender
global CreatorAddress
==
assert
itxn_begin
int pay
itxn_field TypeEnum
txn Sender
itxn_field CloseRemainderTo
itxn_submit
int 1
return
// create
create_0:
store 34
store 33
store 32
byte 0x6f70656e
int 0
app_global_put
byte 0x72657375626d6974
load 34
app_global_put
byte 0x7175657374696f6e
load 32
extract 2 0
app_global_put
load 33
load 33
int 2
int 0
*
extract_uint16
int 0
int 1
+
int 7
==
bnz create_0_l20
load 33
int 2
int 0
*
int 2
+
extract_uint16
create_0_l2:
substring3
store 35
byte 0x6f7074696f6e5f6e616d655f00
load 35
extract 2 0
app_global_put
byte 0x6f7074696f6e5f636f756e745f00
int 0
app_global_put
load 33
load 33
int 2
int 1
*
extract_uint16
int 1
int 1
+
int 7
==
bnz create_0_l19
load 33
int 2
int 1
*
int 2
+
extract_uint16
create_0_l4:
substring3
store 35
byte 0x6f7074696f6e5f6e616d655f01
load 35
extract 2 0
app_global_put
byte 0x6f7074696f6e5f636f756e745f01
int 0
app_global_put
load 33
load 33
int 2
int 2
*
extract_uint16
int 2
int 1
+
int 7
==
bnz create_0_l18
load 33
int 2
int 2
*
int 2
+
extract_uint16
create_0_l6:
substring3
store 35
byte 0x6f7074696f6e5f6e616d655f02
load 35
extract 2 0
app_global_put
byte 0x6f7074696f6e5f636f756e745f02
int 0
app_global_put
load 33
load 33
int 2
int 3
*
extract_uint16
int 3
int 1
+
int 7
==
bnz create_0_l17
load 33
int 2
int 3
*
int 2
+
extract_uint16
create_0_l8:
substring3
store 35
byte 0x6f7074696f6e5f6e616d655f03
load 35
extract 2 0
app_global_put
byte 0x6f7074696f6e5f636f756e745f03
int 0
app_global_put
load 33
load 33
int 2
int 4
*
extract_uint16
int 4
int 1
+
int 7
==
bnz create_0_l16
load 33
int 2
int 4
*
int 2
+
extract_uint16
create_0_l10:
substring3
store 35
byte 0x6f7074696f6e5f6e616d655f04
load 35
extract 2 0
app_global_put
byte 0x6f7074696f6e5f636f756e745f04
int 0
app_global_put
load 33
load 33
int 2
int 5
*
extract_uint16
int 5
int 1
+
int 7
==
bnz create_0_l15
load 33
int 2
int 5
*
int 2
+
extract_uint16
create_0_l12:
substring3
store 35
byte 0x6f7074696f6e5f6e616d655f05
load 35
extract 2 0
app_global_put
byte 0x6f7074696f6e5f636f756e745f05
int 0
app_global_put
load 33
load 33
int 2
int 6
*
extract_uint16
int 6
int 1
+
int 7
==
bnz create_0_l14
load 33
int 2
int 6
*
int 2
+
extract_uint16
b create_0_l21
create_0_l14:
load 33
len
b create_0_l21
create_0_l15:
load 33
len
b create_0_l12
create_0_l16:
load 33
len
b create_0_l10
create_0_l17:
load 33
len
b create_0_l8
create_0_l18:
load 33
len
b create_0_l6
create_0_l19:
load 33
len
b create_0_l4
create_0_l20:
load 33
len
b create_0_l2
create_0_l21:
substring3
store 35
byte 0x6f7074696f6e5f6e616d655f06
load 35
extract 2 0
app_global_put
byte 0x6f7074696f6e5f636f756e745f06
int 0
app_global_put
retsub
// open
open_1:
byte 0x6f70656e
app_global_get
!
assert
byte 0x6f70656e
int 1
app_global_put
retsub
// close
close_2:
byte 0x6f70656e
app_global_get
assert
byte 0x6f70656e
int 0
app_global_put
retsub
// submit
submit_3:
store 36
load 36
int 7
<
assert
byte 0x6f7074696f6e5f636f756e745f00
int 13
load 36
setbyte
store 37
txn Sender
box_get
store 40
store 39
load 40
bz submit_3_l2
byte 0x72657375626d6974
app_global_get
assert
byte 0x6f7074696f6e5f636f756e745f00
int 13
load 39
btoi
setbyte
store 38
load 38
load 38
app_global_get
int 1
-
app_global_put
submit_3_l2:
txn Sender
byte 0x00
int 0
load 36
setbyte
box_put
load 37
load 37
app_global_get
int 1
+
app_global_put
retsub
// status
status_4:
byte 0x7175657374696f6e
app_global_get
store 4
load 4
len
itob
demo_qr_code()